home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue41 / Diagram / JimShape.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1998-11-24  |  54.9 KB  |  1,960 lines

  1. unit JimShape;
  2.  
  3. {$B-}
  4.  
  5. interface
  6.  
  7. uses
  8.   Windows, Controls, Classes, Forms, ExtCtrls, Graphics, Messages;
  9.  
  10. type
  11.   TjimTextShape = class;
  12.  
  13.  
  14.   TjimCustomShape = class(TGraphicControl)
  15.     // All controls descend from this, to help with streaming and unique naming
  16.   private
  17.     FCanProcessMouseMsg : Boolean;
  18.     FCaption            : TjimTextShape;
  19.     FSelected           : Boolean;
  20.     FWasCovered         : Boolean;
  21.  
  22.   protected
  23.     procedure SetCaption(Value : TjimTextShape); virtual;
  24.     procedure MouseDown(Button : TMouseButton;Shift : TShiftState;
  25.                         X,Y : Integer); override;
  26.     procedure MouseUp(Button : TMouseButton;Shift : TShiftState;
  27.                       X,Y : Integer); override;
  28.     function  GetCustomShapeAtPos(X,Y : Integer) : TjimCustomShape;
  29.  
  30.     property CanProcessMouseMsg : Boolean read FCanProcessMouseMsg
  31.                                           write FCanProcessMouseMsg;
  32.     procedure SetParent(AParent : TWinControl); override;
  33.     procedure SetSelected(Value : Boolean); virtual;
  34.     procedure Notification(AComponent : TComponent;Operation : TOperation); override;
  35.   public
  36.     constructor Create(AOwner : TComponent); override;
  37.     destructor  Destroy; override;
  38.  
  39.     procedure SetBounds(ALeft,ATop,AWidth,AHeight : Integer); override;
  40.     procedure AlignCaption(Alignment : TAlignment);
  41.  
  42.     // Class methods to save and load all TjimCustomShape components
  43.     // that are children of a given control. They are class methods so that an
  44.     // instance of TjimCustomShape is not required
  45.     class procedure SaveToFile(const FileName : string;ParentControl : TWinControl);
  46.     class procedure LoadFromFile(const FileName : string;ParentControl : TWinControl);
  47.     class procedure DeleteAllShapes(ParentControl : TWinControl);
  48.     class procedure DeleteSelectedShapes(ParentControl : TWinControl);
  49.     class procedure UnselectAllShapes(ParentControl : TWinControl);
  50.  
  51.     property Selected : Boolean read FSelected write SetSelected;
  52.   published
  53.     property Caption : TjimTextShape read FCaption write SetCaption;
  54.  
  55.     // Make these properties available
  56.     property OnClick;
  57.     property OnDblClick;
  58.   end;
  59.  
  60.  
  61.   TjimMoveableShape = class(TjimCustomShape)
  62.   private
  63.     FOrigin   : TPoint;
  64.     FMoving   : Boolean;
  65.   protected
  66.     procedure StartMove(X,Y : Integer);
  67.     procedure Move(DeltaX,DeltaY : Integer);
  68.     procedure EndMove;
  69.     function  ValidMove(DeltaX,DeltaY : Integer) : Boolean;
  70.     procedure MoveShapes(DeltaX,DeltaY : Integer);
  71.     procedure MouseDown(Button : TMouseButton;Shift : TShiftState;
  72.                         X,Y : Integer); override;
  73.     procedure MouseMove(Shift : TShiftState;X,Y : Integer); override;
  74.     procedure MouseUp(Button : TMouseButton;Shift : TShiftState;
  75.                       X,Y : Integer); override;
  76.  
  77.     property Moving   : Boolean read FMoving write FMoving;
  78.   public
  79.     constructor Create(AOwner : TComponent); override;
  80.   end;
  81.  
  82.  
  83.   TjimSizingMode = (smTopLeft,smTop,smTopRight,smLeft,smRight,
  84.                     smBottomLeft,smBottom,smBottomRight,smNone);
  85.  
  86.  
  87.   TjimSizeableShape = class(TjimMoveableShape)
  88.   private
  89.     FSizingMode     : TjimSizingMode;
  90.     FSizeOrigin     : TPoint;
  91.     FSizeRectHeight : Integer;
  92.     FSizeRectWidth  : Integer;
  93.     FMinHeight      : Integer;
  94.     FMinWidth       : Integer;
  95.   protected
  96.     procedure SetSelected(Value : Boolean); override;
  97.     procedure Paint; override;
  98.     procedure DrawSizingRects;
  99.     function  GetSizeRect(SizeRectType : TjimSizingMode) : TRect;
  100.     procedure CheckForSizeRects(X,Y : Integer);
  101.     procedure ResizeControl(X,Y : Integer);
  102.     procedure MouseDown(Button : TMouseButton;Shift : TShiftState;
  103.                         X,Y : Integer); override;
  104.     procedure MouseMove(Shift : TShiftState;X,Y : Integer); override;
  105.     procedure MouseUp(Button : TMouseButton;Shift : TShiftState;
  106.                       X,Y : Integer); override;
  107.  
  108.     property  SizingMode     : TjimSizingMode read FSizingMode write FSizingMode;
  109.     property  SizeRectHeight : Integer read FSizeRectHeight write FSizeRectHeight;
  110.     property  SizeRectWidth  : Integer read FSizeRectWidth write FSizeRectWidth;
  111.     property  MinHeight      : Integer read FMinHeight write FMinHeight;
  112.     property  MinWidth       : Integer read FMinWidth write FMinWidth;
  113.   public
  114.     constructor Create(AOwner : TComponent); override;
  115.  
  116.     procedure SetBounds(ALeft,ATop,AWidth,AHeight : Integer); override;
  117.   end;
  118.  
  119.  
  120.   TjimTextShape = class(TjimSizeableShape)
  121.   private
  122.     FText     : string;
  123.     FAutosize : Boolean;
  124.     FFont     : TFont;
  125.  
  126.     procedure SetText(Value : string);
  127.     procedure SetAutosize(Value : Boolean);
  128.     procedure SetFont(Value : TFont);
  129.     procedure FontChanged(Sender : TObject);
  130.   protected
  131.     procedure RefreshText;
  132.     procedure SetParent(AParent : TWinControl); override;
  133.     procedure Paint; override;
  134.   public
  135.     constructor Create(AOwner : TComponent); override;
  136.     destructor  Destroy; override;
  137.  
  138.     procedure SetBounds(ALeft,ATop,AWidth,AHeight : Integer); override;
  139.   published
  140.     property Text     : string read FText write SetText;
  141.     property Autosize : Boolean read FAutosize write SetAutosize;
  142.     property Font     : TFont read FFont write SetFont;
  143.   end;
  144.  
  145.  
  146.   TjimBitmapShape = class(TjimMoveableShape)
  147.   private
  148.     FImages     : TImageList;
  149.     FImageIndex : Integer;
  150.  
  151.     procedure SetImages(Value : TImageList);
  152.     procedure SetImageIndex(Value : Integer);
  153.   protected
  154.     procedure SetSelected(Value : Boolean); override;
  155.     procedure Paint; override;
  156.     procedure Notification(AComponent : TComponent;Operation : TOperation); override;
  157.   public
  158.     constructor Create(AOwner : TComponent); override;
  159.   published
  160.     property Images     : TImageList read FImages write SetImages;
  161.     property ImageIndex : Integer read FImageIndex write SetImageIndex;
  162.   end;
  163.  
  164.  
  165.   TjimStandardShape = class(TjimSizeableShape)
  166.   private
  167.     FShapeType  : TShapeType;
  168.     FLineColour : TColor;
  169.  
  170.     procedure SetShapeType(Value : TShapeType);
  171.   protected
  172.     procedure Paint; override;
  173.   public
  174.     constructor Create(AOwner : TComponent); override;
  175.   published
  176.     property ShapeType  : TShapeType read FShapeType write SetShapeType;
  177.     property LineColour : TColor read FLineColour write FLineColour default clBlack;
  178.   end;
  179.  
  180.  
  181.   TjimConnectionSide = (csLeft,csRight,csTop,csBottom);
  182.  
  183.  
  184.   TjimConnection = class(TPersistent)
  185.   private
  186.     FShape  : TjimCustomShape;
  187.     FSide   : TjimConnectionSide;  // Side to connect to
  188.     FOffset : Integer;             // Distance from top or left of side
  189.   public
  190.     constructor Create;
  191.  
  192.     procedure Assign(Source : TPersistent); override;
  193.     // Gets connection point in parent's coordinates
  194.     function ConnPoint(TerminatorRect : TRect): TPoint;
  195.     // Gets terminator connection point in parent's coordinates
  196.     function TermPoint(TerminatorRect : TRect): TPoint;
  197.     // Functions to get boundaries of the terminators
  198.     function LeftMost(TerminatorRect : TRect): TPoint;
  199.     function RightMost(TerminatorRect : TRect): TPoint;
  200.     function TopMost(TerminatorRect : TRect): TPoint;
  201.     function BottomMost(TerminatorRect : TRect): TPoint;
  202.   published
  203.     property Shape  : TjimCustomShape read FShape write FShape;
  204.     property Side   : TjimConnectionSide read FSide write FSide;
  205.     property Offset : Integer read FOffset write FOffset;
  206.   end;
  207.  
  208.  
  209.   TjimConnector = class(TjimCustomShape)
  210.   private
  211.     FLineWidth     : Integer;
  212.     FLineColour    : TColor;
  213.     // The shapes connected by this control
  214.     FStartConn     : TjimConnection;
  215.     FEndConn       : TjimConnection;
  216.     // Area of the terminator symbol to be drawn (in horizontal position)
  217.     FStartTermRect : TRect;
  218.     FEndTermRect   : TRect;
  219.     // Used to track required movement of the caption
  220.     FMidPoint      : TPoint;
  221.  
  222.     procedure SetLineWidth(Value : Integer);
  223.     function  GetConn(Index : Integer) : TjimConnection;
  224.     procedure SetConn(Index : Integer;Value : TjimConnection);
  225.     function  GetTermRect(Index : Integer) : TRect;
  226.     procedure SetTermRect(Index : Integer;Value : TRect);
  227.     procedure CheckSize(var AWidth,AHeight : Integer);
  228.   protected
  229.     procedure SetCaption(Value : TjimTextShape); override;
  230.     procedure Paint; override;
  231.     procedure Notification(AComponent : TComponent;Operation : TOperation); override;
  232.     // For drawing arrows etc. Called from Paint.
  233.     procedure DrawStartTerminator; virtual;
  234.     procedure DrawEndTerminator; virtual;
  235.     procedure MoveCaption;
  236.     // Converts point from parent's coordinates to own coordinates
  237.     function  Convert(APoint : TPoint) : TPoint;
  238.     function  IsConnected(ConnectedShape : TjimCustomShape) : Boolean;
  239.   public
  240.     constructor Create(AOwner : TComponent); override;
  241.     destructor  Destroy; override;
  242.  
  243.     // Restrict the minimum size
  244.     procedure SetBounds(ALeft,ATop,AWidth,AHeight : Integer); override;
  245.     // Called when moving one of the connected shapes
  246.     procedure SetBoundingRect;
  247.     procedure SetConnections(TheStartConn,TheEndConn : TjimConnection);
  248.     function  GetMidPoint : TPoint;
  249.  
  250.     property StartTermRect : TRect index 1 read GetTermRect write SetTermRect;
  251.     property EndTermRect   : TRect index 2 read GetTermRect write SetTermRect;
  252.   published
  253.     // Publish these properties so that component streaming can be used to
  254.     // store them in a file
  255.     property LineWidth : Integer read FLineWidth write SetLineWidth default 1;
  256.     property LineColour : TColor read FLineColour write FLineColour default clBlack;
  257.     property StartConn : TjimConnection index 1 read GetConn write SetConn;
  258.     property EndConn   : TjimConnection index 2 read GetConn write SetConn;
  259.   end;
  260.  
  261.  
  262.   TjimSingleHeadArrow = class(TjimConnector)
  263.   protected
  264.     procedure DrawArrowHead(ConnPt,TermPt : TPoint);
  265.     procedure DrawEndTerminator; override;
  266.   public
  267.     constructor Create(AOwner : TComponent); override;
  268.   end;
  269.  
  270.  
  271.   TjimBluntSingleHeadArrow = class(TjimSingleHeadArrow)
  272.   protected
  273.     procedure DrawStartTerminator; override;
  274.   public
  275.     constructor Create(AOwner : TComponent); override;
  276.   end;
  277.  
  278.  
  279.   TjimDoubleHeadArrow = class(TjimSingleHeadArrow)
  280.   protected
  281.     procedure DrawStartTerminator; override;
  282.   public
  283.     constructor Create(AOwner : TComponent); override;
  284.   end;
  285.  
  286.  
  287. implementation
  288.  
  289. uses
  290.   SysUtils, ImgList, Dialogs;
  291.  
  292.  
  293. type
  294.   // This type is purely so that can acccess the protected MouseDown method
  295.   TjimMdControl = class(TControl);
  296.  
  297.  
  298. var
  299.   FShapeCount : Integer;
  300.   // Used in unique naming scheme. It is global in this unit to enable a
  301.   // 'memory' of the component names used during the lifetime of this unit.
  302.  
  303.  
  304. procedure NoLessThan(var Value : Integer;Limit : Integer);
  305. begin {NoLessThan}
  306.   if Value < Limit then begin
  307.     Value := Limit;
  308.   end;
  309. end;  {NoLessThan}
  310.  
  311.  
  312. function RectHeight(ARect : TRect) : Integer;
  313. begin {RectHeight}
  314.   Result := ARect.Bottom - ARect.Top;
  315. end;  {RectHeight}
  316.  
  317.  
  318. function RectWidth(ARect : TRect) : Integer;
  319. begin {RectWidth}
  320.   Result := ARect.Right - ARect.Left;
  321. end;  {RectWidth}
  322.  
  323.  
  324. function InRect(X,Y : Integer;ARect : TRect) : Boolean;
  325. begin {InRect}
  326.   Result := (X >= ARect.Left) and (X <= ARect.Right) and
  327.             (Y >= ARect.Top) and (Y <= ARect.Bottom);
  328. end;  {InRect}
  329.  
  330.  
  331. function Min(A : array of Integer) : Integer;
  332.   var
  333.     i : Integer;
  334. begin {Min}
  335.   Result := 0;  // Purely to stop compiler warnings
  336.  
  337.   for i := Low(A) to High(A) do begin
  338.     if i = Low(A) then begin
  339.       Result := A[i]
  340.     end else if A[i] < Result then begin
  341.       Result := A[i];
  342.     end;
  343.   end;
  344. end;  {Min}
  345.  
  346.  
  347. function Max(A : array of Integer) : Integer;
  348.   var
  349.     i : Integer;
  350. begin {Max}
  351.   Result := 0;  // Purely to stop compiler warnings
  352.  
  353.   for i := Low(A) to High(A) do begin
  354.     if i = Low(A) then begin
  355.       Result := A[i]
  356.     end else if A[i] > Result then begin
  357.       Result := A[i];
  358.     end;
  359.   end;
  360. end;  {Max}
  361.  
  362.  
  363. // ---------------------------- TjimCustomShape ------------------------------
  364.  
  365. constructor TjimCustomShape.Create(AOwner : TComponent);
  366.   var
  367.     AlreadyUsed : Boolean;
  368.     i           : Integer;
  369.     TempName    : string;
  370. begin {Create}
  371.   inherited Create(AOwner);
  372.   FCanProcessMouseMsg := True;
  373.   FCaption            := nil;
  374.   FSelected           := False;
  375.   FWasCovered         := False;
  376.  
  377.   // Give the component a name and ensure that it is unique
  378.   repeat
  379.     // Use a local variable to hold the name, so that don't get exceptions
  380.     // raised on duplicate names
  381.     TempName := 'Shape' + IntToStr(FShapeCount);
  382.     Inc(FShapeCount);
  383.     AlreadyUsed := False;
  384.  
  385.     // Loop through all the components on the form to ensure that this name
  386.     // is not already in use
  387.     for i := 0 to Owner.ComponentCount - 1 do begin
  388.       if Owner.Components[i].Name = TempName then begin
  389.         // Try the next component name as this one is used already
  390.         AlreadyUsed := True;
  391.         Break;
  392.       end;
  393.     end;
  394.   until not AlreadyUsed;
  395.  
  396.   Name := TempName;
  397. end;  {Create}
  398.  
  399.  
  400. destructor TjimCustomShape.Destroy;
  401.   var
  402.     i : Integer;
  403. begin {Destroy}
  404.   FCaption.Free;
  405.  
  406.   // First check that this control has been placed on a form
  407.   if Assigned(Parent) then begin
  408.     // Search parent control for TjimConnector components that connect
  409.     // to this component
  410.     i := 0;
  411.  
  412.     while i < Parent.ControlCount do begin
  413.       if (Parent.Controls[i] is TjimConnector) and
  414.          (TjimConnector(Parent.Controls[i]).IsConnected(Self)) then begin
  415.         Parent.Controls[i].Free;
  416.       end else begin
  417.         Inc(i);
  418.       end;
  419.     end;
  420.   end;
  421.  
  422.   inherited Destroy;
  423. end;  {Destroy}
  424.  
  425.  
  426. procedure TjimCustomShape.SetCaption(Value : TjimTextShape);
  427. begin {SetCaption}
  428.   if (Value = nil) and Assigned(FCaption) then begin
  429.     FCaption.Free;
  430.     FCaption := nil;
  431.   end else if (Value <> FCaption) then begin
  432.     FCaption        := Value;
  433.     FCaption.Parent := Self.Parent;
  434.     // Ensure the caption gets aligned correctly. Ths only needs to happen if
  435.     // the caption has not already been set in place (it will already be in the
  436.     // right place if we are loading this from a file).
  437.     if (FCaption.Left = 0) and (FCaption.Top = 0) then begin
  438.       AlignCaption(taCenter);
  439.     end;
  440.   end;
  441. end;  {SetCaption}
  442.  
  443.  
  444. procedure TjimCustomShape.SetParent(AParent : TWinControl);
  445. begin {SetParent}
  446.   inherited SetParent(AParent);
  447.  
  448.   if Assigned(FCaption) then begin
  449.     FCaption.Parent := AParent;
  450.   end;
  451. end;  {SetParent}
  452.  
  453.  
  454. procedure TjimCustomShape.SetSelected(Value : Boolean);
  455. begin {SetSelected}
  456.   FSelected := Value;
  457.  
  458.   if Assigned(FCaption) then begin
  459.     FCaption.SetSelected(Value);
  460.   end;
  461. end;  {SetSelected}
  462.  
  463.  
  464. procedure TjimCustomShape.SetBounds(ALeft,ATop,AWidth,AHeight : Integer);
  465.   var
  466.     i : Integer;
  467. begin {SetBounds}
  468.   inherited SetBounds(ALeft,ATop,AWidth,AHeight);
  469.  
  470.   if not Assigned(Parent) then begin
  471.     Exit;
  472.   end;
  473.  
  474.   // Search parent control for TjimConnector components
  475.   for i := 0 to Parent.ControlCount - 1 do begin
  476.     if Parent.Controls[i] is TjimConnector then begin
  477.       if TjimConnector(Parent.Controls[i]).IsConnected(Self) then begin
  478.         // Resize the connector, but don't draw it yet
  479.         TjimConnector(Parent.Controls[i]).SetBoundingRect;
  480.       end;
  481.     end;
  482.   end;
  483. end;  {SetBounds}
  484.  
  485.  
  486. procedure TjimCustomShape.Notification(AComponent : TComponent;Operation : TOperation);
  487. begin {Notification}
  488.   inherited Notification(AComponent,Operation);
  489.  
  490.   if Operation = opRemove then begin
  491.     if AComponent = FCaption then begin
  492.       FCaption := nil;
  493.     end;
  494.   end;
  495. end;  {Notification}
  496.  
  497.  
  498. procedure TjimCustomShape.MouseDown(Button : TMouseButton;Shift : TShiftState;
  499.                                     X,Y : Integer);
  500.   var
  501.     TempPt       : TPoint;
  502.     CoveredShape : TjimCustomShape;
  503. begin {MouseDown}
  504.   if FCanProcessMouseMsg then begin
  505.     BringToFront;
  506.     MouseCapture := True;
  507.     inherited MouseDown(Button,Shift,X,Y);
  508.     Exit;
  509.   end;
  510.  
  511.   // Pass message on to any covered control capable of handling it
  512.   CoveredShape := GetCustomShapeAtPos(X,Y);
  513.   TempPt       := Point(X,Y);
  514.   MouseCapture := False;
  515.  
  516.   if CoveredShape <> nil then begin
  517.     SendToBack;
  518.     // Convert coordinates to covered shape's coordinates
  519.     TempPt := CoveredShape.ScreenToClient(ClientToScreen(TempPt));
  520.     // Send the mouse down message to the covered shape
  521.     CoveredShape.MouseDown(Button,Shift,TempPt.X,TempPt.Y);
  522.     // Flag the control as having been covered because we lose a mouse click
  523.     CoveredShape.FWasCovered := True;
  524.   end else if Assigned(Parent) then begin
  525.     // Send mouse down message to Parent. The typecast is purely to gain access
  526.     // to the Parent.MouseDown method. Need to convert coordinates to parent's
  527.     // coordinates
  528.     TempPt := Parent.ScreenToClient(ClientToScreen(TempPt));
  529.     TjimMdControl(Parent).MouseDown(Button,Shift,TempPt.X,TempPt.Y);
  530.   end;
  531. end;  {MouseDown}
  532.  
  533.  
  534. procedure TjimCustomShape.MouseUp(Button : TMouseButton;Shift : TShiftState;
  535.                                   X,Y : Integer);
  536. begin {MouseUp}
  537.   inherited MouseUp(Button,Shift,X,Y);
  538.  
  539.   if FWasCovered then begin
  540.     // We will lose a mouse click, so replace it
  541.     Click;
  542.     FWasCovered := False;
  543.   end;
  544. end;  {MouseUp}
  545.  
  546.  
  547. function TjimCustomShape.GetCustomShapeAtPos(X,Y : Integer) : TjimCustomShape;
  548.   var
  549.     i  : Integer;
  550.     Pt : TPoint;
  551. begin {GetCustomShapeAtPos}
  552.   Result := nil;
  553.  
  554.   if not Assigned(Parent) then begin
  555.     Exit;
  556.   end;
  557.  
  558.   Pt := Parent.ScreenToClient(ClientToScreen(Point(X,Y)));
  559.  
  560.   for i := 0 to Parent.ControlCount - 1 do begin
  561.     if (Parent.Controls[i] <> Self) and
  562.        (Parent.Controls[i] is TjimCustomShape) and
  563.        TjimCustomShape(Parent.Controls[i]).CanProcessMouseMsg and
  564.        InRect(Pt.X,Pt.Y,Parent.Controls[i].BoundsRect) then begin
  565.       Result := TjimCustomShape(Parent.Controls[i]);
  566.       Exit;
  567.     end;
  568.   end;
  569. end;  {GetCustomShapeAtPos}
  570.  
  571.  
  572. procedure TjimCustomShape.AlignCaption(Alignment : TAlignment);
  573.   var
  574.     ALeft,ATop,AWidth,AHeight : Integer;
  575. begin {AlignCaption}
  576.   if not Assigned(FCaption) then begin
  577.     Exit;
  578.   end;
  579.  
  580.   ALeft   := Left;
  581.   ATop    := Top + Height + 5;
  582.   AWidth  := FCaption.Width;
  583.   AHeight := FCaption.Height;
  584.  
  585.   case Alignment of
  586.     taLeftJustify  : ALeft := Left;
  587.     taRightJustify : ALeft := Left + Width - 1;
  588.     taCenter       : ALeft := Left + ((Width - FCaption.Width) div 2);
  589.   end;
  590.  
  591.   FCaption.SetBounds(ALeft,ATop,AWidth,AHeight);
  592. end;  {AlignCaption}
  593.  
  594.  
  595. class procedure TjimCustomShape.SaveToFile(const FileName : string;
  596.                                            ParentControl : TWinControl);
  597.   var
  598.     FS       : TFileStream;
  599.     Writer   : TWriter;
  600.     RealName : string;
  601. begin {SaveToFile}
  602.   FS     := TFileStream.Create(Filename,fmCreate or fmShareDenyWrite);
  603.   Writer := TWriter.Create(FS,1024);
  604.  
  605.   try
  606.     Writer.Root        := ParentControl.Owner;
  607.     RealName           := ParentControl.Name;
  608.     ParentControl.Name := '';
  609.     Writer.WriteComponent(ParentControl);
  610.     ParentControl.Name := RealName;
  611.   finally
  612.     Writer.Free;
  613.     FS.Free;
  614.   end;
  615. end;  {SaveToFile}
  616.  
  617.  
  618. class procedure TjimCustomShape.LoadFromFile(const FileName : string;
  619.                                              ParentControl : TWinControl);
  620.   var
  621.     FS       : TFileStream;
  622.     Reader   : TReader;
  623.     RealName : string;
  624. begin {LoadFromFile}
  625.   DeleteAllShapes(ParentControl);
  626.  
  627.   FS     := TFileStream.Create(Filename,fmOpenRead or fmShareDenyWrite);
  628.   Reader := TReader.Create(FS,1024);
  629.  
  630.   try
  631.     // Save the parent's name, in case we are reading into a different
  632.     // control than we saved the diagram from
  633.     RealName    := ParentControl.Name;
  634.     Reader.Root := ParentControl.Owner;
  635.     Reader.BeginReferences;
  636.     Reader.ReadComponent(ParentControl);
  637.     Reader.FixupReferences;
  638.     // Restore the parent's name
  639.     ParentControl.Name := RealName;
  640.   finally
  641.     Reader.EndReferences;
  642.     Reader.Free;
  643.     FS.Free;
  644.   end;
  645. end;  {LoadFromFile}
  646.  
  647.  
  648. class procedure TjimCustomShape.DeleteAllShapes(ParentControl : TWinControl);
  649.   var
  650.     i : Integer;
  651. begin {DeleteAllShapes}
  652.   // Delete controls from ParentControl
  653.   i := 0;
  654.  
  655.   while i < ParentControl.ControlCount do begin
  656.     if ParentControl.Controls[i] is TjimCustomShape then begin
  657.       ParentControl.Controls[i].Free;
  658.       // Note that there is no need to increment the counter, because the
  659.       // next component (if any) will now be at the same position in Controls[]
  660.     end else begin
  661.       Inc(i);
  662.     end;
  663.   end;
  664. end; {DeleteAllShapes}
  665.  
  666.  
  667. class procedure TjimCustomShape.DeleteSelectedShapes(ParentControl : TWinControl);
  668.   var
  669.     i : Integer;
  670. begin {DeleteSelectedShapes}
  671.   // Delete controls from ParentControl if they are flagged as selected
  672.   i := 0;
  673.  
  674.   while i < ParentControl.ControlCount do begin
  675.     if (ParentControl.Controls[i] is TjimCustomShape) and
  676.        (TjimCustomShape(ParentControl.Controls[i]).Selected) then begin
  677.       ParentControl.Controls[i].Free;
  678.       // Note that there is no need to increment the counter, because the
  679.       // next component (if any) will now be at the same position in Controls[]
  680.     end else begin
  681.       Inc(i);
  682.     end;
  683.   end;
  684. end; {DeleteSelectedShapes}
  685.  
  686.  
  687. class procedure TjimCustomShape.UnselectAllShapes(ParentControl : TWinControl);
  688.   var
  689.     i : Integer;
  690. begin {UnselectAllShapes}
  691.   for i := 0 to ParentControl.ControlCount - 1 do begin
  692.     if ParentControl.Controls[i] is TjimCustomShape then begin
  693.       TjimCustomShape(ParentControl.Controls[i]).Selected := False;
  694.     end;
  695.   end;
  696. end;  {UnselectAllShapes}
  697.  
  698.  
  699. // --------------------------- TjimMoveableShape  ----------------------------
  700.  
  701. constructor TjimMoveableShape.Create(AOwner : TComponent);
  702. begin {Create}
  703.   inherited Create(AOwner);
  704.   Selected  := False;
  705.   Moving    := False;
  706.   FOrigin   := Point(0,0);
  707. end;  {Create}
  708.  
  709.  
  710. procedure TjimMoveableShape.StartMove(X,Y : Integer);
  711. begin {StartMove}
  712.   Selected := True;
  713.   Moving   := True;
  714.   FOrigin  := Point(X,Y);
  715. end;  {StartMove}
  716.  
  717.  
  718. procedure TjimMoveableShape.Move(DeltaX,DeltaY : Integer);
  719. begin {Move}
  720.   SetBounds(Left + DeltaX,Top + DeltaY,Width,Height);
  721. end;  {Move}
  722.  
  723.  
  724. procedure TjimMoveableShape.EndMove;
  725. begin {EndMove}
  726.   Moving   := False;
  727.   FOrigin  := Point(0,0);
  728. end;  {EndMove}
  729.  
  730.  
  731. function TjimMoveableShape.ValidMove(DeltaX,DeltaY : Integer) : Boolean;
  732. begin {ValidMove}
  733.   Result := True;
  734.  
  735.   if not Assigned(Parent) then begin
  736.     Exit;
  737.   end;
  738.  
  739.   if Selected then begin
  740.     Result := (Left + DeltaX >= 0) and
  741.               (Top + DeltaY >= 0) and
  742.               (Left + DeltaX + Width - 1 <
  743.                Parent.ClientRect.Right - Parent.ClientRect.Left) and
  744.               (Top + DeltaY + Height - 1 <
  745.                Parent.ClientRect.Bottom - Parent.ClientRect.Top);
  746.   end;
  747. end;  {ValidMove}
  748.  
  749.  
  750. procedure TjimMoveableShape.MoveShapes(DeltaX,DeltaY : Integer);
  751.   var
  752.     i,Pass      : Integer;
  753.     TempControl : TControl;
  754. begin {MoveShapes}
  755.   if not Assigned(Parent) then begin
  756.     Exit;
  757.   end;
  758.  
  759.   // Do 2 passes through controls. The first one is to check that all
  760.   // movements are valid
  761.   for Pass := 1 to 2 do begin
  762.     for i := 0 to Parent.ControlCount - 1 do begin
  763.       TempControl := Parent.Controls[i];
  764.  
  765.       if TempControl is TjimMoveableShape then begin
  766.         if (Pass = 1) and
  767.            (not TjimMoveableShape(TempControl).ValidMove(DeltaX,DeltaY)) then begin
  768.           Exit;
  769.         end else if (Pass = 2) and TjimMoveableShape(TempControl).Selected then begin
  770.           TjimMoveableShape(TempControl).Move(DeltaX,DeltaY);
  771.         end;
  772.       end;
  773.     end;
  774.   end;
  775. end;  {MoveShapes}
  776.  
  777.  
  778. procedure TjimMoveableShape.MouseDown(Button : TMouseButton;Shift : TShiftState;
  779.                                       X,Y : Integer);
  780. begin {MouseDown}
  781.   inherited MouseDown(Button,Shift,X,Y);
  782.  
  783.   // Only respond to left mouse button events
  784.   if Button <> mbLeft then begin
  785.     Exit;
  786.   end;
  787.  
  788.   // If not holding down the shift key then not doing multiple selection
  789.   if not (ssShift in Shift) then begin
  790.     UnselectAllShapes(Parent);
  791.   end;
  792.  
  793.   // Start moving the component
  794.   StartMove(X,Y);
  795. end;  {MouseDown}
  796.  
  797.  
  798. procedure TjimMoveableShape.MouseMove(Shift : TShiftState;X,Y : Integer);
  799. begin {MouseMove}
  800.   inherited MouseMove(Shift,X,Y);
  801.  
  802.   // Only need to move the component if the left mouse button is being held down
  803.   if not (ssLeft in Shift) then begin
  804.     Moving := False;
  805.     Exit;
  806.   end;
  807.  
  808.   if Moving then begin
  809.     // Move all the selected shapes
  810.     MoveShapes(X - FOrigin.X,Y - FOrigin.Y);
  811.   end;
  812. end;  {MouseMove}
  813.  
  814.  
  815. procedure TjimMoveableShape.MouseUp(Button : TMouseButton;Shift : TShiftState;
  816.                                     X,Y : Integer);
  817.   var
  818.     i           : Integer;
  819.     TempControl : TControl;
  820. begin {MouseUp}
  821.   inherited MouseUp(Button,Shift,X,Y);
  822.  
  823.   // Only interested in left mouse button events
  824.   if Button <> mbLeft then begin
  825.     Exit;
  826.   end;
  827.  
  828.   EndMove;
  829.  
  830.   // If this shape is covering any smaller shapes then send it to the back,
  831.   // so that we can get at the smaller ones
  832.  
  833.   if not Assigned(Parent) then begin
  834.     Exit;
  835.   end;
  836.  
  837.   for i := 0 to Parent.ControlCount - 1 do begin
  838.     TempControl := Parent.Controls[i];
  839.  
  840.     if (TempControl <> Self) and
  841.        (TempControl is TjimCustomShape) and
  842.        TjimCustomShape(TempControl).CanProcessMouseMsg and
  843.        InRect(TempControl.Left,TempControl.Top,BoundsRect) and
  844.        InRect(TempControl.Left + TempControl.Width,
  845.               TempControl.Top + TempControl.Height,BoundsRect) then begin
  846.       // TempControl is not this one, it is a custom shape, that can process
  847.       // mouse messages (eg not a connector), and is completely covered by
  848.       // this control. So bring the convered control to the top of the z-order
  849.       // so that we can access it.
  850.       TempControl.BringToFront;
  851.       Exit;
  852.     end;
  853.   end;
  854. end;  {MouseUp}
  855.  
  856.  
  857. // --------------------------- TjimSizeableShape -----------------------------
  858.  
  859. constructor TjimSizeableShape.Create(AOwner : TComponent);
  860. begin {Create}
  861.   inherited Create(AOwner);
  862.   FSizingMode     := smNone;
  863.   FSizeOrigin     := Point(0,0);
  864.   FSizeRectHeight := 5;
  865.   FSizeRectWidth  := 5;
  866.   FMinHeight      := FSizeRectHeight;
  867.   FMinWidth       := FSizeRectWidth;
  868. end;  {Create}
  869.  
  870.  
  871. procedure TjimSizeableShape.SetSelected(Value : Boolean);
  872. begin {SetSelected}
  873.   if Value <> FSelected then begin
  874.     inherited SetSelected(Value);
  875.     // Force redraw to show sizing rectangles
  876.     Invalidate;
  877.   end;
  878. end;  {SetSelected}
  879.  
  880.  
  881. procedure TjimSizeableShape.Paint;
  882. begin {Paint}
  883.   inherited Paint;
  884.  
  885.   if not Assigned(Parent) then begin
  886.     Exit;
  887.   end;
  888.  
  889.   DrawSizingRects;
  890. end;  {Paint}
  891.  
  892.  
  893. function TjimSizeableShape.GetSizeRect(SizeRectType : TjimSizingMode) : TRect;
  894. begin {GetSizeRect}
  895.   case SizeRectType of
  896.     smTopLeft     : Result := Bounds(0,0,SizeRectWidth,SizeRectHeight);
  897.     smTop         : Result := Bounds(((ClientRect.Right - ClientRect.Left) div 2) -
  898.                                      (SizeRectWidth div 2),
  899.                                      0,
  900.                                      SizeRectWidth,SizeRectHeight);
  901.     smTopRight    : Result := Bounds(ClientRect.Right - SizeRectWidth,0,
  902.                                      SizeRectWidth,SizeRectHeight);
  903.     smLeft        : Result := Bounds(0,
  904.                                      ((ClientRect.Bottom - ClientRect.Top) div 2) -
  905.                                      (SizeRectHeight div 2),
  906.                                      SizeRectWidth,SizeRectHeight);
  907.     smRight       : Result := Bounds(ClientRect.Right - SizeRectWidth,
  908.                                      ((ClientRect.Bottom - ClientRect.Top) div 2) -
  909.                                      (SizeRectHeight div 2),
  910.                                      SizeRectWidth,SizeRectHeight);
  911.     smBottomLeft  : Result := Bounds(0,ClientRect.Bottom - SizeRectHeight,
  912.                                      SizeRectWidth,SizeRectHeight);
  913.     smBottom      : Result := Bounds(((ClientRect.Right - ClientRect.Left) div 2) -
  914.                                      (SizeRectWidth div 2),
  915.                                      ClientRect.Bottom - SizeRectHeight,
  916.                                      SizeRectWidth,SizeRectHeight);
  917.     smBottomRight : Result := Bounds(ClientRect.Right - SizeRectWidth,
  918.                                      ClientRect.Bottom - SizeRectHeight,
  919.                                      SizeRectWidth,SizeRectHeight);
  920.     smNone        : Result := Bounds(0,0,0,0);
  921.    end;
  922. end;  {GetSizeRect}
  923.  
  924.  
  925. procedure TjimSizeableShape.DrawSizingRects;
  926.   var
  927.     OldBrush : TBrush;
  928.     SMode    : TjimSizingMode;
  929. begin {DrawSizingRects}
  930.   if not FSelected then begin
  931.     Exit;
  932.   end;
  933.  
  934.   with Canvas do begin
  935.     // Draw the sizing rectangles
  936.     OldBrush := TBrush.Create;
  937.  
  938.     try
  939.       OldBrush.Assign(Brush);
  940.       Brush.Style := bsSolid;
  941.       Brush.Color := clBlack;
  942.       Pen.Color   := clBlack;
  943.  
  944.       for SMode := smTopLeft to smBottomRight do begin
  945.         FillRect(GetSizeRect(SMode));
  946.       end;
  947.     finally
  948.       Brush.Assign(OldBrush);
  949.       OldBrush.Free;
  950.     end;
  951.   end;
  952. end;  {DrawSizingRects}
  953.  
  954.  
  955. procedure TjimSizeableShape.CheckForSizeRects(X,Y : Integer);
  956.   var
  957.     SMode : TjimSizingMode;
  958. begin {CheckForSizeRects}
  959.   FSizingMode := smNone;
  960.  
  961.   if not Selected then begin
  962.     Exit;
  963.   end;
  964.  
  965.   for SMode := smTopLeft to smBottomRight do begin
  966.     if InRect(X,Y,GetSizeRect(SMode)) then begin
  967.       FSizingMode := SMode;
  968.       Break;
  969.     end;
  970.   end;
  971.  
  972.   case FSizingMode of
  973.     smTopLeft     : Cursor := crSizeNWSE;
  974.     smTop         : Cursor := crSizeNS;
  975.     smTopRight    : Cursor := crSizeNESW;
  976.     smLeft        : Cursor := crSizeWE;
  977.     smRight       : Cursor := crSizeWE;
  978.     smBottomLeft  : Cursor := crSizeNESW;
  979.     smBottom      : Cursor := crSizeNS;
  980.     smBottomRight : Cursor := crSizeNWSE;
  981.     else            Cursor := crDefault;
  982.   end;
  983. end;  {CheckForSizeRects}
  984.  
  985.  
  986. procedure TjimSizeableShape.ResizeControl(X,Y : Integer);
  987.   var
  988.     L,T,W,H,DeltaX,DeltaY : Integer;
  989. begin {ResizeControl}
  990.   L := Left;
  991.   T := Top;
  992.   W := Width;
  993.   H := Height;
  994.   DeltaX := X - FSizeOrigin.X;
  995.   DeltaY := Y - FSizeOrigin.Y;
  996.  
  997.   // Calculate the new boundaries on the control. Also change FSizeOrigin to
  998.   // reflect change in boundaries if necessary.
  999.   case FSizingMode of
  1000.     smTopLeft     : begin
  1001.       // Ensure that don't move the left edge if this would make the
  1002.       // control too narrow
  1003.       if (L + DeltaX >= 0) and (W - DeltaX > MinWidth) then begin
  1004.         L := L + DeltaX;
  1005.         W := W - DeltaX;
  1006.       end;
  1007.  
  1008.       // Ensure that don't move the top edge if this would make the
  1009.       // control too short
  1010.       if (T + DeltaY >= 0) and (H - DeltaY > MinHeight) then begin
  1011.         T := T + DeltaY;
  1012.         H := H - DeltaY;
  1013.       end;
  1014.     end;
  1015.  
  1016.     smTop         : begin
  1017.       if (T + DeltaY >= 0) and (H - DeltaY > MinHeight) then begin
  1018.         T := T + DeltaY;
  1019.         H := H - DeltaY;
  1020.       end;
  1021.     end;
  1022.  
  1023.     smTopRight    : begin
  1024.       W := W + DeltaX;
  1025.  
  1026.       if (T + DeltaY >= 0) and (H - DeltaY > MinHeight) then begin
  1027.         T := T + DeltaY;
  1028.         H := H - DeltaY;
  1029.       end;
  1030.  
  1031.       FSizeOrigin.X := X;
  1032.     end;
  1033.  
  1034.     smLeft        : begin
  1035.       if (L + DeltaX >= 0) and (W - DeltaX > MinWidth) then begin
  1036.         L := L + DeltaX;
  1037.         W := W - DeltaX;
  1038.       end;
  1039.     end;
  1040.  
  1041.     smRight       : begin
  1042.       W             := W + DeltaX;
  1043.       FSizeOrigin.X := X;
  1044.     end;
  1045.  
  1046.     smBottomLeft  : begin
  1047.       if (L + DeltaX >= 0) and (W - DeltaX > MinWidth) then begin
  1048.         L := L + DeltaX;
  1049.         W := W - DeltaX;
  1050.       end;
  1051.  
  1052.       H             := H + DeltaY;
  1053.       FSizeOrigin.Y := Y;
  1054.     end;
  1055.  
  1056.     smBottom      : begin
  1057.       H             := H + DeltaY;
  1058.       FSizeOrigin.X := X;
  1059.       FSizeOrigin.Y := Y;
  1060.     end;
  1061.  
  1062.     smBottomRight : begin
  1063.       W             := W + DeltaX;
  1064.       H             := H + DeltaY;
  1065.       FSizeOrigin.X := X;
  1066.       FSizeOrigin.Y := Y;
  1067.     end;
  1068.  
  1069.     smNone : ;
  1070.   end;
  1071.  
  1072.   SetBounds(L,T,W,H);
  1073. end;  {ResizeControl}
  1074.  
  1075.  
  1076. procedure TjimSizeableShape.MouseDown(Button : TMouseButton;Shift : TShiftState;
  1077.                                       X,Y : Integer);
  1078. begin {MouseDown}
  1079.   if (FSizingMode = smNone) or
  1080.      (Button <> mbLeft) or
  1081.      (ssShift in Shift) then begin
  1082.     // Do moving instead of sizing
  1083.     FSizingMode := smNone;
  1084.     inherited MouseDown(Button,Shift,X,Y);
  1085.     Exit;
  1086.   end;
  1087.  
  1088.   // If sizing then make this the only selected control
  1089.   UnselectAllShapes(Parent);
  1090.   BringToFront;
  1091.   FSelected   := True;
  1092.   FSizeOrigin := Point(X,Y);
  1093. end;  {MouseDown}
  1094.  
  1095.  
  1096. procedure TjimSizeableShape.MouseMove(Shift : TShiftState;X,Y : Integer);
  1097. begin {MouseMove}
  1098.   if Moving then begin
  1099.     inherited MouseMove(Shift,X,Y);
  1100.   end else if (FSizingMode <> smNone) and (ssLeft in Shift) then begin
  1101.     ResizeControl(X,Y);
  1102.   end else begin
  1103.     // Check if over a sizing rectangle
  1104.     CheckForSizeRects(X,Y);
  1105.   end;
  1106. end;  {MouseMove}
  1107.  
  1108.  
  1109. procedure TjimSizeableShape.MouseUp(Button : TMouseButton;Shift : TShiftState;
  1110.                                     X,Y : Integer);
  1111. begin {MouseUp}
  1112.   if Button = mbLeft then begin
  1113.     FSizingMode := smNone;
  1114.   end;
  1115.  
  1116.   inherited MouseUp(Button,Shift,X,Y);
  1117. end;  {MouseUp}
  1118.  
  1119.  
  1120. procedure TjimSizeableShape.SetBounds(ALeft,ATop,AWidth,AHeight : Integer);
  1121. begin {SetBounds}
  1122.   // Check that the control bounds are sensible. The control must be at least
  1123.   // as large as a sizing rectangle
  1124.   NoLessThan(ALeft,0);
  1125.   NoLessThan(ATop,0);
  1126.   NoLessThan(AWidth,FMinWidth);
  1127.   NoLessThan(AHeight,FMinHeight);
  1128.   inherited SetBounds(ALeft,ATop,AWidth,AHeight);
  1129. end;  {SetBounds}
  1130.  
  1131.  
  1132. // ----------------------------- TjimTextShape  ------------------------------
  1133.  
  1134. constructor TjimTextShape.Create(AOwner : TComponent);
  1135. begin {Create}
  1136.   inherited Create(AOwner);
  1137.   FAutosize      := True;
  1138.   FText          := '';
  1139.   FFont          := TFont.Create;
  1140.   FFont.OnChange := FontChanged;
  1141. end;  {Create}
  1142.  
  1143.  
  1144. destructor TjimTextShape.Destroy;
  1145. begin {Destroy}
  1146.   FFont.Free;
  1147.   inherited Destroy;
  1148. end;  {Destroy}
  1149.  
  1150.  
  1151. procedure TjimTextShape.RefreshText;
  1152.   var
  1153.     i,Count : Integer;
  1154.     TempStr : string;
  1155. begin {RefreshText}
  1156.   FMinHeight := FSizeRectHeight;
  1157.   FMinWidth  := FSizeRectWidth;
  1158.   TempStr    := '';
  1159.   Count      := 1;
  1160.  
  1161.   if FAutosize and Assigned(Parent) then begin
  1162.     Canvas.Font := Font;
  1163.  
  1164.     for i := 1 to Length(FText) do begin
  1165.       if FText[i] = #10 then begin
  1166.         // Check the width of this line
  1167.         FMinWidth := Max([FMinWidth,Canvas.TextWidth(TempStr)]);
  1168.         TempStr   := '';
  1169.         // Count the line feeds
  1170.         Inc(Count);
  1171.       end else begin
  1172.         TempStr := TempStr + FText[i];
  1173.       end;
  1174.     end;
  1175.  
  1176.     if Count = 1 then begin
  1177.       // In case there is only one line
  1178.       FMinWidth := Max([FMinWidth,Canvas.TextWidth(FText)]);
  1179.     end;
  1180.  
  1181.     // Calculate the height of the text rectangle
  1182.     FMinHeight := Max([FMinHeight,Canvas.TextHeight(FText) * Count]);
  1183.   end;
  1184.  
  1185.   SetBounds(Left,Top,Width,Height);
  1186. end;  {RefreshText}
  1187.  
  1188.  
  1189. procedure TjimTextShape.SetText(Value : string);
  1190. begin {SetText}
  1191.   if FText <> Value then begin
  1192.     FText := Value;
  1193.     RefreshText;
  1194.   end;
  1195. end;  {SetText}
  1196.  
  1197.  
  1198. procedure TjimTextShape.SetAutosize(Value : Boolean);
  1199. begin {SetAutosize}
  1200.   if FAutosize <> Value then begin
  1201.     FAutosize := Value;
  1202.     RefreshText;
  1203.   end;
  1204. end;  {SetAutosize}
  1205.  
  1206.  
  1207. procedure TjimTextShape.SetFont(Value : TFont);
  1208. begin {SetFont}
  1209.   FFont.Assign(Value);
  1210. end;  {SetFont}
  1211.  
  1212.  
  1213. procedure TjimTextShape.FontChanged(Sender : TObject);
  1214. begin {FontChanged}
  1215.   RefreshText;
  1216. end;  {FontChanged}
  1217.  
  1218.  
  1219. procedure TjimTextShape.SetParent(AParent : TWinControl);
  1220. begin {SetParent}
  1221.   inherited SetParent(AParent);
  1222.   RefreshText;
  1223. end;  {SetParent}
  1224.  
  1225.  
  1226. procedure TjimTextShape.Paint;
  1227.   var
  1228.     TempRect : TRect;
  1229. begin {Paint}
  1230.   if not Assigned(Parent) then begin
  1231.     Exit;
  1232.   end;
  1233.  
  1234.   Canvas.Font := Font;
  1235.   TempRect := ClientRect;  // So can pass as a var parameter
  1236.   DrawText(Canvas.Handle,PChar(FText),Length(FText),TempRect,
  1237.            DT_CENTER or DT_NOPREFIX or DT_WORDBREAK);
  1238.   inherited Paint;
  1239. end;  {Paint}
  1240.  
  1241.  
  1242. procedure TjimTextShape.SetBounds(ALeft,ATop,AWidth,AHeight : Integer);
  1243. begin {SetBounds}
  1244.   // Check that the control bounds are sensible. Note that this also works
  1245.   // if try to set Left, Top etc properties, as their access methods call
  1246.   // SetBounds().
  1247.   NoLessThan(AWidth,FMinWidth);
  1248.   NoLessThan(AHeight,FMinHeight);
  1249.   inherited SetBounds(ALeft,ATop,AWidth,AHeight);
  1250. end;  {SetBounds}
  1251.  
  1252.  
  1253. // ---------------------------- TjimBitmapShape ------------------------------
  1254.  
  1255. constructor TjimBitmapShape.Create(AOwner : TComponent);
  1256. begin {Create}
  1257.   inherited Create(AOwner);
  1258.   FImages     := nil;
  1259.   FImageIndex := 0;
  1260. end;  {Create}
  1261.  
  1262.  
  1263. procedure TjimBitmapShape.SetSelected(Value : Boolean);
  1264. begin {SetSelected}
  1265.   if Value <> FSelected then begin
  1266.     inherited SetSelected(Value);
  1267.     // Force redraw to show focus rectangle
  1268.     Invalidate;
  1269.   end;
  1270. end;  {SetSelected}
  1271.  
  1272.  
  1273. procedure TjimBitmapShape.SetImages(Value : TImageList);
  1274. begin {SetImages}
  1275.   if Value <> FImages then begin
  1276.     FImages := Value;
  1277.  
  1278.     if FImages <> nil then begin
  1279.       // Set the size of the component to the image size
  1280.       SetBounds(Left,Top,FImages.Width,FImages.Height);
  1281.     end;
  1282.   end;
  1283. end;  {SetImages}
  1284.  
  1285.  
  1286. procedure TjimBitmapShape.SetImageIndex(Value : Integer);
  1287. begin {SetImageIndex}
  1288.   if Value <> FImageIndex then begin
  1289.     FImageIndex := Value;
  1290.     Invalidate;
  1291.   end;
  1292. end;  {SetImageIndex}
  1293.  
  1294.  
  1295. procedure TjimBitmapShape.Paint;
  1296.   var
  1297.     OldPen : TPen;
  1298. begin {Paint}
  1299.   inherited Paint;
  1300.  
  1301.   if (not Assigned(Parent)) or
  1302.      (not Assigned(FImages)) or
  1303.      (FImageIndex < 0) or
  1304.      (FImageIndex >= FImages.Count) then begin
  1305.     // The component has not been placed on a form yet, or does not have an
  1306.     // associated image
  1307.     Exit;
  1308.   end;
  1309.  
  1310.   // Draw a focus rectangle
  1311.   OldPen             := Canvas.Pen;
  1312.   Canvas.Pen.Style   := psDot;
  1313.   Canvas.Brush.Style := bsClear;
  1314.  
  1315.   if Selected then begin
  1316.     Canvas.Pen.Mode := pmNot;
  1317.   end else begin
  1318.     Canvas.Pen.Mode := pmNop;
  1319.   end;
  1320.  
  1321.   Canvas.Polyline([Point(0,0),
  1322.                    Point(Width - 1,0),
  1323.                    Point(Width - 1,Height - 1),
  1324.                    Point(0,Height - 1),
  1325.                    Point(0,0)]);
  1326.   Canvas.Pen := OldPen;
  1327.  
  1328.   // Draw the bitmap
  1329.   FImages.DrawingStyle := dsTransparent;
  1330.   FImages.Draw(Canvas,0,0,FImageIndex);
  1331. end;  {Paint}
  1332.  
  1333.  
  1334. procedure TjimBitmapShape.Notification(AComponent : TComponent;Operation : TOperation);
  1335. begin {Notification}
  1336.   inherited Notification(AComponent,Operation);
  1337.  
  1338.   if Operation = opRemove then begin
  1339.     if AComponent = FImages then begin
  1340.       FImages := nil;
  1341.     end;
  1342.   end;
  1343. end;  {Notification}
  1344.  
  1345.  
  1346. // ---------------------------- TtabdStandardShape ---------------------------
  1347.  
  1348. constructor TjimStandardShape.Create(AOwner : TComponent);
  1349. begin {Create}
  1350.   inherited Create(AOwner);
  1351.  
  1352.   // Set a default shape and size and colours
  1353.   FShapeType  := stRectangle;
  1354.   Width       := 100;
  1355.   Height      := 60;
  1356.   FLineColour := clBlack;
  1357. end;  {Create}
  1358.  
  1359.  
  1360. procedure TjimStandardShape.SetShapeType(Value : TShapeType);
  1361. begin {SetShapeType}
  1362.   if FShapeType <> Value then begin
  1363.     FShapeType := Value;
  1364.     Invalidate;
  1365.   end;
  1366. end;  {SetShapeType}
  1367.  
  1368.  
  1369. procedure TjimStandardShape.Paint;
  1370.   var
  1371.     TempRect : TRect;
  1372.     S        : Integer;
  1373. begin {Paint}
  1374.   inherited Paint;
  1375.  
  1376.   if not Assigned(Parent) then begin
  1377.     Exit;
  1378.   end;
  1379.  
  1380.   TempRect := ClientRect;  // So can pass as a var parameter
  1381.   InflateRect(TempRect,-SizeRectWidth,-SizeRectHeight);
  1382.  
  1383.   // Draw shape outline
  1384.   Canvas.Brush.Style := bsClear;
  1385.   Canvas.Pen.Color   := FLineColour;
  1386.   S := Min([TempRect.Right - TempRect.Left + 1,TempRect.Bottom - TempRect.Top + 1]);
  1387.  
  1388.   if FShapeType in [stSquare,stRoundSquare,stCircle] then begin
  1389.     TempRect.Right  := TempRect.Left + S;
  1390.     TempRect.Bottom := TempRect.Top + S;
  1391.   end;
  1392.  
  1393.   case FShapeType of
  1394.     stRectangle,stSquare :
  1395.       Canvas.Rectangle(TempRect.Left,TempRect.Top,TempRect.Right,TempRect.Bottom);
  1396.     stRoundRect,stRoundSquare :
  1397.       Canvas.RoundRect(TempRect.Left,TempRect.Top,TempRect.Right,TempRect.Bottom,
  1398.                        S div 4,S div 4);
  1399.     stCircle,stEllipse :
  1400.       Canvas.Ellipse(TempRect.Left,TempRect.Top,TempRect.Right,TempRect.Bottom);
  1401.   end;
  1402. end;  {Paint}
  1403.  
  1404.  
  1405. // ----------------------------- TjimConnection ------------------------------
  1406.  
  1407. constructor TjimConnection.Create;
  1408. begin {Create}
  1409.   inherited Create;
  1410.   FShape  := nil;
  1411.   FSide   := csRight;
  1412.   FOffset := 0;
  1413. end;  {Create}
  1414.  
  1415.  
  1416. procedure TjimConnection.Assign(Source : TPersistent);
  1417. begin {Assign}
  1418.   if Source is TjimConnection then begin
  1419.     FShape  := TjimConnection(Source).FShape;
  1420.     FSide   := TjimConnection(Source).FSide;
  1421.     FOffset := TjimConnection(Source).FOffset;
  1422.   end else begin
  1423.     inherited Assign(Source);
  1424.   end;
  1425. end;  {Assign}
  1426.  
  1427.  
  1428. function TjimConnection.ConnPoint(TerminatorRect : TRect): TPoint;
  1429.   var
  1430.     X,Y,W : Integer;
  1431. begin {ConnPoint}
  1432.   Result := Point(0,0);
  1433.   X      := 0;
  1434.   Y      := 0;
  1435.   W      := TerminatorRect.Right - TerminatorRect.Left;
  1436.  
  1437.   if FShape = nil then begin
  1438.     Exit;
  1439.   end;
  1440.  
  1441.   case FSide of
  1442.     csLeft   : begin
  1443.       X := FShape.Left - W;
  1444.       Y := FShape.Top + FOffset;
  1445.     end;
  1446.  
  1447.     csRight  : begin
  1448.       X := FShape.Left + FShape.Width - 1 + W;
  1449.       Y := FShape.Top + FOffset;
  1450.     end;
  1451.  
  1452.     csTop    : begin
  1453.       X := FShape.Left + FOffset;
  1454.       Y := FShape.Top - W;
  1455.     end;
  1456.  
  1457.     csBottom : begin
  1458.       X := FShape.Left + FOffset;
  1459.       Y := FShape.Top  + FShape.Height - 1 + W;
  1460.     end;
  1461.   end;
  1462.  
  1463.   Result := Point(X,Y);
  1464. end;  {ConnPoint}
  1465.  
  1466.  
  1467. function TjimConnection.TermPoint(TerminatorRect : TRect): TPoint;
  1468.   var
  1469.     X,Y : Integer;
  1470. begin {TermPoint}
  1471.   Result := Point(0,0);
  1472.   X      := 0;
  1473.   Y      := 0;
  1474.  
  1475.   if FShape = nil then begin
  1476.     Exit;
  1477.   end;
  1478.  
  1479.   case FSide of
  1480.     csLeft   : begin
  1481.       X := FShape.Left;
  1482.       Y := FShape.Top + FOffset;
  1483.     end;
  1484.  
  1485.     csRight  : begin
  1486.       X := FShape.Left + FShape.Width - 1;
  1487.       Y := FShape.Top + FOffset;
  1488.     end;
  1489.  
  1490.     csTop    : begin
  1491.       X := FShape.Left + FOffset;
  1492.       Y := FShape.Top;
  1493.     end;
  1494.  
  1495.     csBottom : begin
  1496.       X := FShape.Left + FOffset;
  1497.       Y := FShape.Top  + FShape.Height - 1;
  1498.     end;
  1499.   end;
  1500.  
  1501.   Result := Point(X,Y);
  1502. end;  {TermPoint}
  1503.  
  1504.  
  1505. function TjimConnection.LeftMost(TerminatorRect : TRect): TPoint;
  1506. begin {LeftMost}
  1507.   Result := TermPoint(TerminatorRect);
  1508.  
  1509.   if FShape = nil then begin
  1510.     Exit;
  1511.   end;
  1512.  
  1513.   case FSide of
  1514.     csLeft   : Result.X := FShape.Left - RectWidth(TerminatorRect);
  1515.     csRight  : Result.X := FShape.Left + FShape.Width;
  1516.     csTop,
  1517.     csBottom : Result.X := FShape.Left + FOffset - (RectHeight(TerminatorRect) div 2);
  1518.   end;
  1519. end;  {LeftMost}
  1520.  
  1521.  
  1522. function TjimConnection.RightMost(TerminatorRect : TRect): TPoint;
  1523. begin {RightMost}
  1524.   Result := TermPoint(TerminatorRect);
  1525.  
  1526.   if FShape = nil then begin
  1527.     Exit;
  1528.   end;
  1529.  
  1530.   case FSide of
  1531.     csLeft   : Result.X := FShape.Left - 1;
  1532.     csRight  : Result.X := FShape.Left + FShape.Width - 1 + RectWidth(TerminatorRect);
  1533.     csTop,
  1534.     csBottom : Result.X := FShape.Left + FOffset + (RectHeight(TerminatorRect) div 2);
  1535.   end;
  1536. end;  {RightMost}
  1537.  
  1538.  
  1539. function TjimConnection.TopMost(TerminatorRect : TRect): TPoint;
  1540. begin {TopMost}
  1541.   Result := TermPoint(TerminatorRect);
  1542.  
  1543.   if FShape = nil then begin
  1544.     Exit;
  1545.   end;
  1546.  
  1547.   case FSide of
  1548.     csLeft,
  1549.     csRight  : Result.Y := FShape.Top + FOffset - (RectHeight(TerminatorRect) div 2);
  1550.     csTop    : Result.Y := FShape.Top - RectWidth(TerminatorRect) - 1;
  1551.     csBottom : Result.Y := FShape.Top + FShape.Height;
  1552.   end;
  1553. end;  {TopMost}
  1554.  
  1555.  
  1556. function TjimConnection.BottomMost(TerminatorRect : TRect): TPoint;
  1557. begin {BottomMost}
  1558.   Result := TermPoint(TerminatorRect);
  1559.  
  1560.   if FShape = nil then begin
  1561.     Exit;
  1562.   end;
  1563.  
  1564.   case FSide of
  1565.     csLeft,
  1566.     csRight  : Result.Y := FShape.Top + FOffset + (RectHeight(TerminatorRect) div 2);
  1567.     csTop    : Result.Y := FShape.Top - 1;
  1568.     csBottom : Result.Y := FShape.Top + FShape.Height + RectWidth(TerminatorRect);
  1569.   end;
  1570. end;  {BottomMost}
  1571.  
  1572.  
  1573. // ----------------------------- TjimConnector -------------------------------
  1574.  
  1575. constructor TjimConnector.Create(AOwner : TComponent);
  1576. begin {Create}
  1577.   inherited Create(AOwner);
  1578.   FCanProcessMouseMsg := False;
  1579.   FLineWidth          := 1;
  1580.   FLineColour         := clBlack;
  1581.   FStartTermRect      := Rect(0,0,0,0);
  1582.   FEndTermRect        := Rect(0,0,0,0);
  1583.   FStartConn          := TjimConnection.Create;
  1584.   FEndConn            := TjimConnection.Create;
  1585.   FMidPoint           := Point(0,0);
  1586. end;  {Create}
  1587.  
  1588.  
  1589. destructor TjimConnector.Destroy;
  1590. begin {Destroy}
  1591.   FStartConn.Free;
  1592.   FEndConn.Free;
  1593.   inherited Destroy;
  1594. end;  {Destroy}
  1595.  
  1596.  
  1597. procedure TjimConnector.Paint;
  1598.   var
  1599.     EndPt : TPoint;
  1600. begin {Paint}
  1601.   inherited Paint;
  1602.  
  1603.   if not Assigned(Parent) then begin
  1604.     Exit;
  1605.   end;
  1606.  
  1607.   if Assigned(FStartConn.Shape) and Assigned(FEndConn.Shape) then begin
  1608.     // Draw the terminators (arrows etc)
  1609.     DrawStartTerminator;
  1610.     DrawEndTerminator;
  1611.  
  1612.     with Canvas do begin
  1613.       // Draw the connecting line
  1614.       Brush.Style := bsClear;
  1615.       Pen.Width   := FLineWidth;
  1616.       Pen.Color   := FLineColour;
  1617.       // Convert from Parent coordinates to control coordinates
  1618.       PenPos      := Convert(FStartConn.ConnPoint(FStartTermRect));
  1619.       EndPt       := Convert(FEndConn.ConnPoint(FEndTermRect));
  1620.       LineTo(EndPt.X,EndPt.Y);
  1621.     end;
  1622.   end;
  1623. end;  {Paint}
  1624.  
  1625.  
  1626. procedure TjimConnector.Notification(AComponent : TComponent;Operation : TOperation);
  1627. begin {Notification}
  1628.   inherited Notification(AComponent,Operation);
  1629.  
  1630.   if Operation = opRemove then begin
  1631.     if AComponent = FStartConn.FShape then begin
  1632.       FStartConn.FShape := nil;
  1633.     end;
  1634.  
  1635.     if AComponent = FEndConn.FShape then begin
  1636.       FEndConn.FShape := nil;
  1637.     end;
  1638.   end;
  1639. end;  {Notification}
  1640.  
  1641.  
  1642. procedure TjimConnector.DrawStartTerminator;
  1643. begin {DrawStartTerminator}
  1644. end;  {DrawStartTerminator}
  1645.  
  1646.  
  1647. procedure TjimConnector.DrawEndTerminator;
  1648. begin {DrawEndTerminator}
  1649. end;  {DrawEndTerminator}
  1650.  
  1651.  
  1652. procedure TjimConnector.MoveCaption;
  1653.   var
  1654.     NewMidPoint               : TPoint;
  1655.     ALeft,ATop,ARight,ABottom : Integer;
  1656. begin {MoveCaption}
  1657.   if Assigned(FCaption) then begin
  1658.     NewMidPoint := GetMidPoint;
  1659.     // Move the caption relative to the mid point of the connector
  1660.     // Not resizing anything, just moving an unconnected shape, so can use
  1661.     // faster update method than SetBounds
  1662.     FCaption.Invalidate;
  1663.     ALeft   := FCaption.Left + NewMidPoint.X - FMidPoint.X;
  1664.     ATop    := FCaption.Top + NewMidPoint.Y - FMidPoint.Y;
  1665.     ARight  := ALeft + FCaption.Width;
  1666.     ABottom := ATop + FCaption.Height;
  1667.     FCaption.UpdateBoundsRect(Rect(ALeft,ATop,ARight,ABottom));
  1668.     // Save the new mid point
  1669.     FMidPoint := NewMidPoint;
  1670.   end;
  1671. end;  {MoveCaption}
  1672.  
  1673.  
  1674. procedure TjimConnector.CheckSize(var AWidth,AHeight : Integer);
  1675. begin {CheckSize}
  1676.   // Ensure the control is at least as big as the line width
  1677.   NoLessThan(AHeight,FLineWidth);
  1678.   NoLessThan(AWidth,FLineWidth);
  1679.   // Ensure the control is at least as big as the start terminator rectangle
  1680.   NoLessThan(AHeight,RectHeight(FStartTermRect));
  1681.   NoLessThan(AWidth,RectWidth(FStartTermRect));
  1682.   // Ensure the control is at least as big as the end terminator rectangle
  1683.   NoLessThan(AHeight,RectHeight(FEndTermRect));
  1684.   NoLessThan(AWidth,RectWidth(FEndTermRect));
  1685. end;  {CheckSize}
  1686.  
  1687.  
  1688. procedure TjimConnector.SetBounds(ALeft,ATop,AWidth,AHeight : Integer);
  1689. begin {SetBounds}
  1690.   CheckSize(AWidth,AHeight);
  1691.   // Resize the connector
  1692.   inherited SetBounds(ALeft,ATop,AWidth,AHeight);
  1693.   // Move the caption
  1694.   MoveCaption;
  1695. end;  {SetBounds}
  1696.  
  1697.  
  1698. procedure TjimConnector.SetBoundingRect;
  1699.   var
  1700.     ALeft,ATop,AWidth,AHeight : Integer;
  1701. begin {SetBoundingRect}
  1702.   if (FStartConn.Shape = nil) or (FEndConn.Shape = nil) then begin
  1703.     Exit;
  1704.   end;
  1705.  
  1706.   ALeft   := Min([FStartConn.LeftMost(FStartTermRect).X,
  1707.                   FEndConn.LeftMost(FEndTermRect).X]);
  1708.   ATop    := Min([FStartConn.TopMost(FStartTermRect).Y,
  1709.                   FEndConn.TopMost(FEndTermRect).Y]);
  1710.   AWidth  := Max([FStartConn.RightMost(FStartTermRect).X,
  1711.                   FEndConn.RightMost(FEndTermRect).X]) -
  1712.              ALeft + 2;
  1713.   AHeight := Max([FStartConn.BottomMost(FStartTermRect).Y,
  1714.                   FEndConn.BottomMost(FEndTermRect).Y]) -
  1715.              ATop + 2;
  1716.   CheckSize(AWidth,AHeight);
  1717.   Invalidate;
  1718.   UpdateBoundsRect(Rect(ALeft,ATop,ALeft + AWidth - 1,ATop + AHeight - 1));
  1719.   MoveCaption;
  1720. end;  {SetBoundingRect}
  1721.  
  1722.  
  1723. procedure TjimConnector.SetLineWidth(Value : Integer);
  1724. begin {SetLineWidth}
  1725.   // Ensure that can always see the line!
  1726.   if Value >= 1 then begin
  1727.     FLineWidth := Value;
  1728.   end;
  1729. end;  {SetLineWidth}
  1730.  
  1731.  
  1732. function TjimConnector.GetConn(Index : Integer) : TjimConnection;
  1733. begin {GetConn}
  1734.   Result := nil;
  1735.  
  1736.   case Index of
  1737.     1 : Result := FStartConn;
  1738.     2 : Result := FEndConn;
  1739.   end;
  1740. end;  {GetConn}
  1741.  
  1742.  
  1743. procedure TjimConnector.SetConn(Index : Integer;Value : TjimConnection);
  1744. begin {SetConn}
  1745.   case Index of
  1746.     1 : FStartConn.Assign(Value);
  1747.     2 : FEndConn.Assign(Value);
  1748.   end;
  1749.  
  1750.   SetBoundingRect;
  1751. end;  {SetConn}
  1752.  
  1753.  
  1754. procedure TjimConnector.SetConnections(TheStartConn,TheEndConn : TjimConnection);
  1755. begin {SetConnections}
  1756.   StartConn := TheStartConn;
  1757.   EndConn   := TheEndConn;
  1758. end;  {SetConnections}
  1759.  
  1760.  
  1761. function TjimConnector.GetTermRect(Index : Integer) : TRect;
  1762. begin {GetTermRect}
  1763.   case Index of
  1764.     1 : Result := FStartTermRect;
  1765.     2 : Result := FEndTermRect;
  1766.   end;
  1767. end;  {GetTermRect}
  1768.  
  1769.  
  1770. procedure TjimConnector.SetTermRect(Index : Integer;Value : TRect);
  1771. begin {SetTermRect}
  1772.   if (Value.Right - Value.Left >= 0) and (Value.Bottom - Value.Top >= 0) then begin
  1773.     case Index of
  1774.       1 : FStartTermRect := Value;
  1775.       2 : FEndTermRect   := Value;
  1776.     end;
  1777.   end;
  1778. end;  {SetTermRect}
  1779.  
  1780.  
  1781. procedure TjimConnector.SetCaption(Value : TjimTextShape);
  1782. begin {SetCaption}
  1783.   inherited SetCaption(Value);
  1784.   MoveCaption;
  1785. end;  {SetCaption}
  1786.  
  1787.  
  1788. function TjimConnector.Convert(APoint : TPoint) : TPoint;
  1789. begin {Convert}
  1790.   Result := ScreenToClient(Parent.ClientToScreen(APoint));
  1791. end;  {Convert}
  1792.  
  1793.  
  1794. function TjimConnector.IsConnected(ConnectedShape : TjimCustomShape) : Boolean;
  1795. begin {IsConnected}
  1796.   Result := (FStartConn.Shape = ConnectedShape) or
  1797.             (FEndConn.Shape = ConnectedShape);
  1798. end;  {IsConnected}
  1799.  
  1800.  
  1801. function TjimConnector.GetMidPoint : TPoint;
  1802.   var
  1803.     A,B : TPoint;
  1804. begin {GetMidPoint}
  1805.   A := FStartConn.ConnPoint(FStartTermRect);
  1806.   B := FEndConn.ConnPoint(FEndTermRect);
  1807.   Result := Point(Min([A.X,B.X]) + Abs(A.X - B.X) div 2,
  1808.                   Min([A.Y,B.Y]) + Abs(A.Y - B.Y) div 2);
  1809. end;  {GetMidPoint}
  1810.  
  1811.  
  1812. // ------------------------- TjimSingleHeadArrow ---------------------------
  1813.  
  1814. constructor TjimSingleHeadArrow.Create(AOwner : TComponent);
  1815. begin {Create}
  1816.   inherited Create(AOwner);
  1817.   EndTermRect := Rect(0,0,25,10);
  1818. end;  {Create}
  1819.  
  1820.  
  1821. procedure TjimSingleHeadArrow.DrawArrowHead(ConnPt,TermPt : TPoint);
  1822.   var
  1823.     PointPt,Corner1Pt,Corner2Pt : TPoint;
  1824. begin {DrawArrowHead}
  1825.   with Canvas do begin
  1826.     Brush.Style := bsSolid;
  1827.     Brush.Color := FLineColour;
  1828.     Pen.Color   := FLineColour;
  1829.  
  1830.     // Draw a line connecting the Conn and Term points
  1831.     PenPos    := ConnPt;
  1832.     LineTo(TermPt.X,TermPt.Y);
  1833.     // Set the basic points (to be modified depending on arrow head direction
  1834.     PointPt   := TermPt;
  1835.     Corner1Pt := ConnPt;
  1836.     Corner2Pt := ConnPt;
  1837.  
  1838.     if ConnPt.X < TermPt.X then begin
  1839.       // Draw a right pointing arrow head
  1840.       Inc(Corner1Pt.X,10);
  1841.       Inc(Corner2Pt.X,10);
  1842.       Dec(Corner1Pt.Y,RectHeight(EndTermRect) div 2);
  1843.       Inc(Corner2Pt.Y,RectHeight(EndTermRect) div 2);
  1844.     end else if ConnPt.X > TermPt.X then begin
  1845.       // Draw a left pointing arrow head
  1846.       Dec(Corner1Pt.X,10);
  1847.       Dec(Corner2Pt.X,10);
  1848.       Dec(Corner1Pt.Y,RectHeight(EndTermRect) div 2);
  1849.       Inc(Corner2Pt.Y,RectHeight(EndTermRect) div 2);
  1850.     end else if ConnPt.Y < TermPt.Y then begin
  1851.       // Draw a down pointing arrow head
  1852.       Inc(Corner1Pt.Y,10);
  1853.       Inc(Corner2Pt.Y,10);
  1854.       Dec(Corner1Pt.X,RectHeight(EndTermRect) div 2);
  1855.       Inc(Corner2Pt.X,RectHeight(EndTermRect) div 2);
  1856.     end else begin
  1857.       // Draw a up pointing arrow head
  1858.       Dec(Corner1Pt.Y,10);
  1859.       Dec(Corner2Pt.Y,10);
  1860.       Dec(Corner1Pt.X,RectHeight(EndTermRect) div 2);
  1861.       Inc(Corner2Pt.X,RectHeight(EndTermRect) div 2);
  1862.     end;
  1863.  
  1864.     Polygon([PointPt,Corner1Pt,Corner2Pt]);
  1865.   end;
  1866. end;  {DrawArrowHead}
  1867.  
  1868.  
  1869. procedure TjimSingleHeadArrow.DrawEndTerminator;
  1870.   var
  1871.     ConnPt,TermPt : TPoint;
  1872. begin {DrawEndTerminator}
  1873.   inherited DrawEndTerminator;
  1874.  
  1875.   if Assigned(FEndConn.Shape) then begin
  1876.     ConnPt := Convert(FEndConn.ConnPoint(EndTermRect));
  1877.     TermPt := Convert(FEndConn.TermPoint(EndTermRect));;
  1878.     DrawArrowHead(ConnPt,TermPt);
  1879.   end;
  1880. end;  {DrawEndTerminator}
  1881.  
  1882.  
  1883. // ------------------------ TjimBluntSingleHeadArrow -------------------------
  1884.  
  1885. constructor TjimBluntSingleHeadArrow.Create(AOwner : TComponent);
  1886. begin {Create}
  1887.   inherited Create(AOwner);
  1888.   StartTermRect := Rect(0,0,10,10);
  1889. end;  {Create}
  1890.  
  1891.  
  1892. procedure TjimBluntSingleHeadArrow.DrawStartTerminator;
  1893.   var
  1894.     ConnPt,TermPt : TPoint;
  1895. begin {DrawStartTerminator}
  1896.   inherited DrawStartTerminator;
  1897.  
  1898.   if not Assigned(FStartConn.Shape) then begin
  1899.     Exit;
  1900.   end;
  1901.  
  1902.   ConnPt := Convert(FStartConn.ConnPoint(StartTermRect));
  1903.   TermPt := Convert(FStartConn.TermPoint(StartTermRect));;
  1904.  
  1905.   with Canvas do begin
  1906.     // Draw a line connecting the Conn and Term points
  1907.     Pen.Color := FLineColour;
  1908.     PenPos    := ConnPt;
  1909.     LineTo(TermPt.X,TermPt.Y);
  1910.   end;
  1911. end;  {DrawStartTerminator}
  1912.  
  1913.  
  1914. // --------------------------- TjimDoubleHeadArrow ---------------------------
  1915.  
  1916. constructor TjimDoubleHeadArrow.Create(AOwner : TComponent);
  1917. begin {Create}
  1918.   inherited Create(AOwner);
  1919.   StartTermRect := EndTermRect;
  1920. end;  {Create}
  1921.  
  1922.  
  1923. procedure TjimDoubleHeadArrow.DrawStartTerminator;
  1924.   var
  1925.     ConnPt,TermPt : TPoint;
  1926. begin {DrawStartTerminator}
  1927.   inherited DrawStartTerminator;
  1928.  
  1929.   if Assigned(FStartConn.Shape) then begin
  1930.     ConnPt := Convert(FStartConn.ConnPoint(StartTermRect));
  1931.     TermPt := Convert(FStartConn.TermPoint(StartTermRect));;
  1932.     DrawArrowHead(ConnPt,TermPt);
  1933.   end;
  1934. end;  {DrawStartTerminator}
  1935.  
  1936.  
  1937. // ------------------ Initialisation and cleanup routines --------------------
  1938.  
  1939. procedure RegisterStorageClasses;
  1940. begin {RegisterStorageClasses}
  1941.   RegisterClasses([TjimCustomShape,
  1942.                    TjimMoveableShape,
  1943.                    TjimSizeableShape,
  1944.                    TjimConnection,
  1945.                    TjimConnector,
  1946.                    TjimSingleHeadArrow,
  1947.                    TjimBluntSingleHeadArrow,
  1948.                    TjimDoubleHeadArrow,
  1949.                    TjimBitmapShape,
  1950.                    TjimTextShape,
  1951.                    TjimStandardShape]);
  1952. end;  {RegisterStorageClasses}
  1953.  
  1954.  
  1955. initialization
  1956.   RegisterStorageClasses;
  1957.   FShapeCount := 1;
  1958.  
  1959. end.
  1960.